<!DOCTYPE html>
<html>
  <head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
  <meta name="description" content="tong.li&#39;s blog">
  <meta name="keyword" content="彤哥哥博客，95后技术爱好者,现就职于同程旅行/同程艺龙上海分公司，专注于互联网技术分享的平台。">
  
    <link rel="shortcut icon" href="/css/images/icon.png">
  
  <title>
    
      线程切换导致ThreadLocal数据丢失分析 | 彤哥哥的博客
    
  </title>
  <link href="https://cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
  <link href="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.css" rel="stylesheet">
  <link href="https://cdn.staticfile.org/highlight.js/9.12.0/styles/tomorrow-night.min.css" rel="stylesheet">
  
<link rel="stylesheet" href="/css/style.css">

  
  <script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script>
  <script src="https://cdn.staticfile.org/geopattern/1.2.3/js/geopattern.min.js"></script>
  <script src="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.js"></script>
  
    
<script src="/js/qrious.js"></script>

  
  
  
  
    <!-- MathJax support START -->
    <script type="text/x-mathjax-config">
      MathJax.Hub.Config({
        tex2jax: {
          inlineMath: [ ['$','$'], ["\\(","\\)"]  ],
          processEscapes: true,
          skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
        }
      });
    </script>

    <script type="text/x-mathjax-config">
      MathJax.Hub.Queue(function() {
        var all = MathJax.Hub.getAllJax(), i;
        for (i=0; i < all.length; i += 1) {
          all[i].SourceElement().parentNode.className += ' has-jax';
        }
      });
    </script>
    <script type="text/javascript" src="https://cdn.staticfile.org/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
    <!-- MathJax support END -->
  


  
  
    
<script src="/js/local-search.js"></script>


<meta name="generator" content="Hexo 5.4.2"></head>
<div class="wechat-share">
  <img src="/css/images/logo.png" />
</div>
  <body>
    <header class="header fixed-header">
  <div class="header-container">
    <a class="home-link" href="/">
      <div class="logo"></div>
      <span>彤哥哥的博客</span>
    </a>
    <ul class="right-list">
      
        <li class="list-item">
          
            <a href="/" class="item-link">主页</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/series/" class="item-link">分类</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/tags/" class="item-link">标签</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/archives/" class="item-link">归档</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/project/" class="item-link">项目</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/about/" class="item-link">关于</a>
          
        </li>
      
      
        <li class="menu-item menu-item-search right-list">
    <a role="button" class="popup-trigger">
        <i class="fa fa-search fa-fw"></i>
    </a>
</li>
      
    </ul>
    <div class="menu">
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
    </div>
    <div class="menu-mask">
      <ul class="menu-list">
        
          <li class="menu-item">
            
              <a href="/" class="menu-link">主页</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/series/" class="menu-link">分类</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/tags/" class="menu-link">标签</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/archives/" class="menu-link">归档</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/project/" class="menu-link">项目</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/about/" class="menu-link">关于</a>
            
          </li>
        
      </ul>
    </div>
    
      <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
            <span class="search-icon">
                <i class="fa fa-search"></i>
            </span>
            <div class="search-input-container">
                <input autocomplete="off" autocapitalize="off"
                    placeholder="Please enter your keyword(s) to search." spellcheck="false"
                    type="search" class="search-input">
            </div>
            <span class="popup-btn-close">
                <i class="fa fa-times-circle"></i>
            </span>
        </div>
        <div id="search-result">
            <div id="no-result">
                <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
            </div>
        </div>
    </div>
</div>
    
  </div>
</header>

    <div id="article-banner">
  <h2>线程切换导致ThreadLocal数据丢失分析</h2>
  <p class="post-date">2018-03-15</p>
  <div class="arrow-down">
    <a href="javascript:;"></a>
  </div>
</div>
<main class="app-body flex-box">
  <!-- Article START -->
  <article class="post-article">
    <section class="markdown-content"><p>最近在使用Spring Cloud过程中,经常会遇见线程隔离(切换).导致ThreadLocal数据丢失.例如调用其他服务获取不到Threadlocal没有数据,服务之间传递请求头传递失败.通过查阅相关文档才发现:<br>用Hystrix实现断路器,Zuul中默认使用的是信号量,其他默认都是线程隔离.具体文档如下(可参考<a target="_blank" rel="noopener" href="https://github.com/Netflix/Hystrix/wiki/Configuration#executionisolationstrategy">Hystrix WIKI</a>)：</p>
<blockquote>
<p>Thread or Semaphore</p>
<ul>
<li>The default, and the recommended setting, is to run HystrixCommands using thread isolation (THREAD) and HystrixObservableCommands using semaphore isolation (SEMAPHORE).</li>
<li>Commands executed in threads have an extra layer of protection against latencies beyond what network timeouts can offer.</li>
<li>Generally the only time you should use semaphore isolation for HystrixCommands is when the call is so high volume (hundreds per second, per instance) that the overhead of separate threads is too high; this typically only applies to non-network calls.</li>
</ul>
</blockquote>
<p>在使用线程隔离的时候，有个问题是必须要解决的，那就是在某些业务场景下通过ThreadLocal来在线程里传递数据，用信号量是没问题的，从请求进来，但后续的流程都是通一个线程。<br>当隔离模式为线程时，Hystrix会将请求放入Hystrix的线程池中去执行，这个时候某个请求就有A线程变成B线程了，ThreadLocal必然消失了.</p>
<h2 id="模拟实现"><a href="#模拟实现" class="headerlink" title="模拟实现"></a>模拟实现</h2> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">public class CustomThreadLocal &#123;</span><br><span class="line">    static ThreadLocal&lt;String&gt; threadLocal = new ThreadLocal&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    public static void main(String[] args) &#123;</span><br><span class="line">        new Thread(new Runnable() &#123;</span><br><span class="line"></span><br><span class="line">            @Override</span><br><span class="line">            public void run() &#123;</span><br><span class="line">                CustomThreadLocal.threadLocal.set(&quot;彤哥哥&quot;);</span><br><span class="line">                new Service().call();</span><br><span class="line"></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;).start();</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">class Service &#123;</span><br><span class="line">    public void call() &#123;</span><br><span class="line">        System.out.println(&quot;Service:&quot; + Thread.currentThread().getName());</span><br><span class="line">        System.out.println(&quot;Service:&quot; + CustomThreadLocal.threadLocal.get());</span><br><span class="line">        new Dao().call();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">class Dao &#123;</span><br><span class="line">    public void call() &#123;</span><br><span class="line">        System.out.println(&quot;==========================&quot;);</span><br><span class="line">        System.out.println(&quot;Dao:&quot; + Thread.currentThread().getName());</span><br><span class="line">        System.out.println(&quot;Dao:&quot; + CustomThreadLocal.threadLocal.get());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>我们在主类中定义了一个ThreadLocal用来传递数据，然后起了一个线程，在线程中调用Service中的call方法，并且往Threadlocal中设置了一个值，在Service中获取ThreadLocal中的值，然后再调用Dao中的call方法，也是获取ThreadLocal中的值，我们运行下看效果：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Service:Thread-0</span><br><span class="line">Service:彤哥哥</span><br><span class="line">==========================</span><br><span class="line">Dao:Thread-0</span><br><span class="line">Dao:彤哥哥</span><br></pre></td></tr></table></figure>
<p>从运行结果来看,同一个线程中能够获得ThreadLocal的值.这个没错,接下来,将Serice类中的call()方法稍微改造一下:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">public void call() &#123;</span><br><span class="line">    System.out.println(&quot;Service:&quot; + Thread.currentThread().getName());</span><br><span class="line">    System.out.println(&quot;Service:&quot; + CustomThreadLocal.threadLocal.get());</span><br><span class="line">    //new Dao().call();</span><br><span class="line">    new Thread(new Runnable() &#123;</span><br><span class="line">        @Override</span><br><span class="line">        public void run() &#123;</span><br><span class="line">            new Dao().call();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;).start();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>再次运行结果如下:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Service:Thread-0</span><br><span class="line">Service:彤哥哥</span><br><span class="line">==========================</span><br><span class="line">Dao:Thread-1</span><br><span class="line">Dao:null</span><br></pre></td></tr></table></figure>
<p>由此可见是两个不同的线程,在运行Dao中的call()方法进行了线程切换,所以ThreadLocal获取到的数据未null.</p>
<h2 id="InheritableThreadLocal引入"><a href="#InheritableThreadLocal引入" class="headerlink" title="InheritableThreadLocal引入"></a>InheritableThreadLocal引入</h2><p>既然遇到问题就该解决,那么如何解决呢?<br>其实解决起来很简单,只需要改一行代码即可.</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">static ThreadLocal&lt;String&gt; threadLocal = new InheritableThreadLocal&lt;&gt;();</span><br></pre></td></tr></table></figure>
<p>将Threadlocal改成子类InheritableThreadLocal后运行结果:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Service:Thread-0</span><br><span class="line">Service:彤哥哥</span><br><span class="line">==========================</span><br><span class="line">Dao:Thread-1</span><br><span class="line">Dao:彤哥哥</span><br></pre></td></tr></table></figure>
<p>非常完美的解决了线程切换导致ThreadLocal拿不到值而产生的问题.</p>
<h2 id="深入InheritableThreadLocal原理"><a href="#深入InheritableThreadLocal原理" class="headerlink" title="深入InheritableThreadLocal原理"></a>深入InheritableThreadLocal原理</h2><p>要先了解InheritableThreadLocal原理,首先清楚ThreadLocal的原理.话不多说,先分析一下ThreadLocal的原理:</p>
<ul>
<li>每个线程都有一个ThreadLocalMap类型的threadLocals属性,ThreadLocalMap类相当于一个Map,key 是 ThreadLocal本身,value就是我们设置的值.<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">public class Thread implements Runnable &#123;</span><br><span class="line">    ThreadLocal.ThreadLocalMap threadLocals = null;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
<li>当我们通过threadLocal.set(“彤哥哥”)的时候,就是在这个线程中的threadLocals属性中放入一个键值对,key是 当前线程,value就是你设置的值。</li>
<li>当我们通过 threadlocal.get()方法的时候,就是根据当前线程作为key来获取这个线程设置的值.<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">public class ThreadLocal&lt;T&gt; &#123;</span><br><span class="line"></span><br><span class="line">    public void set(T value) &#123;</span><br><span class="line">        //获取当前的线程对象</span><br><span class="line">        Thread t = Thread.currentThread();</span><br><span class="line">        //获取当前线程对象中的threadLocals属性</span><br><span class="line">        ThreadLocalMap map = getMap(t);</span><br><span class="line">        if (map != null)</span><br><span class="line">            map.set(this, value);</span><br><span class="line">        else</span><br><span class="line">            createMap(t, value);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    ThreadLocalMap getMap(Thread t) &#123;</span><br><span class="line">        return t.threadLocals;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    public T get() &#123;</span><br><span class="line">        Thread t = Thread.currentThread();</span><br><span class="line">        ThreadLocalMap map = getMap(t);</span><br><span class="line">        if (map != null) &#123;</span><br><span class="line">            ThreadLocalMap.Entry e = map.getEntry(this);</span><br><span class="line">            if (e != null) &#123;</span><br><span class="line">                @SuppressWarnings(&quot;unchecked&quot;)</span><br><span class="line">                T result = (T)e.value;</span><br><span class="line">                return result;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        return setInitialValue();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
通过上面的介绍我们可以了解到threadlocal能够传递数据是用Thread.currentThread()当前线程来获取,也就是只要在相同的线程中就可以获取到前面设置进去的值.<br>如果在threadlocal设置完值之后,下步的操作重新创建了一个线程,这个时候Thread.currentThread()就已经变了,那么肯定是拿不到之前设置的值.具体的问题复现可以参考上面我的代码.<br>那为什么InheritableThreadLocal就可以呢?<br>InheritableThreadLocal这个类继承了ThreadLocal,重写了3个方法,在当前线程上创建一个新的线程实例Thread时,会把这些线程变量从当前线程传递给新的线程实例.<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">public class InheritableThreadLocal&lt;T&gt; extends ThreadLocal&lt;T&gt; &#123;</span><br><span class="line">    </span><br><span class="line">    protected T childValue(T parentValue) &#123;</span><br><span class="line">        return parentValue;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">   </span><br><span class="line">    ThreadLocalMap getMap(Thread t) &#123;</span><br><span class="line">       return t.inheritableThreadLocals;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">   </span><br><span class="line">    void createMap(Thread t, T firstValue) &#123;</span><br><span class="line">        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
通过上面的代码我们可以看到InheritableThreadLocal 重写了childValue, getMap,createMap三个方法,我们往里面set值的时候,值保存到了inheritableThreadLocals里面,而不是之前的threadLocals<br>那么关键的点来了,为什么当创建新的线程池,可以获取到上个线程里的threadLocal中的值呢?原因就是在新创建线程的时候,会把之前线程的inheritableThreadLocals赋值给新线程的inheritableThreadLocals,通过这种方式实现了数据的传递.<br>源码最开始在Thread的init()方法中,如下:<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">public</span><br><span class="line">class Thread implements Runnable &#123;</span><br><span class="line">    private void init(ThreadGroup g, Runnable target, String name,</span><br><span class="line">                      long stackSize, AccessControlContext acc,</span><br><span class="line">                      boolean inheritThreadLocals) &#123;</span><br><span class="line">        //代码省略......              </span><br><span class="line">        if (inheritThreadLocals &amp;&amp; parent.inheritableThreadLocals != null)</span><br><span class="line">             //创建新的ThreadLocalMap并复制给当前线程的inheritableThreadLocals对象</span><br><span class="line">            this.inheritableThreadLocals =</span><br><span class="line">                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);</span><br><span class="line">        //代码省略......</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) &#123;</span><br><span class="line">        return new ThreadLocalMap(parentMap);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    /**</span><br><span class="line">     * 赋值代码</span><br><span class="line">     */</span><br><span class="line">     private ThreadLocalMap(ThreadLocalMap parentMap) &#123;</span><br><span class="line">        Entry[] parentTable = parentMap.table;</span><br><span class="line">        int len = parentTable.length;</span><br><span class="line">        setThreshold(len);</span><br><span class="line">        table = new Entry[len];</span><br><span class="line"></span><br><span class="line">        for (int j = 0; j &lt; len; j++) &#123;</span><br><span class="line">            Entry e = parentTable[j];</span><br><span class="line">            if (e != null) &#123;</span><br><span class="line">                @SuppressWarnings(&quot;unchecked&quot;)</span><br><span class="line">                ThreadLocal&lt;Object&gt; key = (ThreadLocal&lt;Object&gt;) e.get();</span><br><span class="line">                if (key != null) &#123;</span><br><span class="line">                    Object value = key.childValue(e.value);</span><br><span class="line">                    Entry c = new Entry(key, value);</span><br><span class="line">                    int h = key.threadLocalHashCode &amp; (len - 1);</span><br><span class="line">                    while (table[h] != null)</span><br><span class="line">                        h = nextIndex(h, len);</span><br><span class="line">                    table[h] = c;</span><br><span class="line">                    size++;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
到此为止,通过inheritableThreadLocals我们可以在父线程创建子线程的时候将Local中的值传递给子线程,这个特性已经能够满足大部分的需求了.<br>但是还有一个很严重的问题是如果是在线程复用的情况下就会出问题,比如线程池中去使用inheritableThreadLocals进行传值,因为inheritableThreadLocals 只是会再新创建线程的时候进行传值,<br>线程复用并不会做这个操作,那么要解决这个问题就得自己去扩展线程类实现这个功能.</li>
</ul>
<h2 id="阿里解决之道"><a href="#阿里解决之道" class="headerlink" title="阿里解决之道"></a>阿里解决之道</h2><p>开源的世界应有尽有,为了解决上述遗留的问题,阿里开源了一款Java框架: <a target="_blank" rel="noopener" href="https://github.com/alibaba/transmittable-thread-local">transmittable-thread-local</a><br>其主要功能就是解决在使用线程池等会缓存线程的组件情况下,提供ThreadLocal值的传递功能,解决异步执行时上下文传递的问题.<br>JDK的InheritableThreadLocal类可以完成父线程到子线程的值传递.但对于使用线程池等会缓存线程的组件的情况,线程由线程池创建好,并且线程是缓存起来反复使用的;<br>这时父子线程关系的ThreadLocal值传递已经没有意义,应用需要的实际上是把任务提交给线程池时的ThreadLocal值传递到任务执行时.<br>transmittable-thread-local使用方式分为三种:修饰Runnable和Callable,修饰线程池,Java Agent来修饰JDK线程池实现类.<br>接下来给大家演示下线程池的修饰方式,首先来一个非正常的案例,代码如下:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line">public class CustomThreadLocal &#123;</span><br><span class="line"></span><br><span class="line">    static ThreadLocal&lt;String&gt; threadLocal = new InheritableThreadLocal&lt;&gt;();</span><br><span class="line">    /**</span><br><span class="line">     * 创建一个固定大小为2的线程池</span><br><span class="line">     */</span><br><span class="line">    static ExecutorService pool = Executors.newFixedThreadPool(2);</span><br><span class="line"></span><br><span class="line">    public static void main(String[] args) &#123;</span><br><span class="line">        for(int i=0;i&lt;100;i++) &#123;</span><br><span class="line">            int j = i;</span><br><span class="line">            pool.execute(new Thread(new Runnable() &#123;</span><br><span class="line"></span><br><span class="line">                @Override</span><br><span class="line">                public void run() &#123;</span><br><span class="line">                    CustomThreadLocal.threadLocal.set(&quot;彤哥哥&quot;+j);</span><br><span class="line">                    new Service().call();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;));</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">class Service &#123;</span><br><span class="line">    public void call() &#123;</span><br><span class="line">        CustomThreadLocal.pool.execute(new Runnable() &#123;</span><br><span class="line"></span><br><span class="line">            @Override</span><br><span class="line">            public void run() &#123;</span><br><span class="line">                new Dao().call();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">class Dao &#123;</span><br><span class="line">    public void call() &#123;</span><br><span class="line">        System.out.println(&quot;Dao:&quot; + CustomThreadLocal.threadLocal.get());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>运行上面的代码,出现的结果是不正确的,输出结果如下:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">Dao:彤哥哥99</span><br><span class="line">Dao:彤哥哥99</span><br><span class="line">Dao:彤哥哥62</span><br><span class="line">Dao:彤哥哥99</span><br><span class="line">Dao:彤哥哥62</span><br><span class="line">Dao:彤哥哥99</span><br><span class="line">Dao:彤哥哥99</span><br><span class="line">Dao:彤哥哥62</span><br><span class="line">Dao:彤哥哥99</span><br><span class="line">Dao:彤哥哥62</span><br><span class="line">Dao:彤哥哥62</span><br><span class="line">Dao:彤哥哥99</span><br><span class="line">省略之后的结果...</span><br></pre></td></tr></table></figure>
<p>正确的应该是从0-99不能有重复,由于线程的复用,值被替换掉了才会出现不正确的结果.<br>接下来使用transmittable-thread-local来改造有问题的代码,添加transmittable-thread-local的Maven依赖：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;com.alibaba&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;transmittable-thread-local&lt;/artifactId&gt;</span><br><span class="line">    &lt;version&gt;2.2.0&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure>
<p>只需要修改2个地方,修饰线程池和替换InheritableThreadLocal:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">static TransmittableThreadLocal&lt;String&gt; threadLocal = new TransmittableThreadLocal&lt;&gt;();</span><br><span class="line">static ExecutorService pool =  TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));</span><br></pre></td></tr></table></figure>
<p>正确结果如下:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">Dao:彤哥哥1</span><br><span class="line">Dao:彤哥哥2</span><br><span class="line">Dao:彤哥哥4</span><br><span class="line">Dao:彤哥哥5</span><br><span class="line">Dao:彤哥哥3</span><br><span class="line">Dao:彤哥哥6</span><br><span class="line">Dao:彤哥哥7</span><br><span class="line">Dao:彤哥哥8</span><br><span class="line">Dao:彤哥哥9</span><br><span class="line">Dao:彤哥哥10</span><br><span class="line">Dao:彤哥哥11</span><br><span class="line">省略之后的结果...</span><br></pre></td></tr></table></figure>
<p>到这里我们就已经可以完美的解决线程中,线程池中ThreadLocal数据的传递了,题主趁着中午喝茶的时间,无意间找到其他解决方案,下节继续用其他方式进行解决.</p>
</section>
    <!-- Tags START -->
    
      <div class="tags">
        <span>Tags:</span>
        
  <a href="/tags#Java" >
    <span class="tag-code">Java</span>
  </a>

      </div>
    
    <!-- Tags END -->
    <!-- NAV START -->
    
  <div class="nav-container">
    <!-- reverse left and right to put prev and next in a more logic postition -->
    
      <a class="nav-left" href="/2018/02/07/%E4%BD%BF%E7%94%A8Jenkins%E8%BF%9B%E8%A1%8C%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90/">
        <span class="nav-arrow">← </span>
        
          使用Jenkins进行持续集成
        
      </a>
    
    
      <a class="nav-right" href="/2018/03/27/%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F-Spring%E5%8D%95%E4%BE%8B%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90/">
        
          单例模式-Spring单例实现原理分析
        
        <span class="nav-arrow"> →</span>
      </a>
    
  </div>

    <!-- NAV END -->
    <!-- 打赏 START -->
    
      <div class="money-like">
        <div class="reward-btn">
          赏
          <span class="money-code">
            <span class="alipay-code">
              <div class="code-image"></div>
              <b>使用支付宝打赏</b>
            </span>
            <span class="wechat-code">
              <div class="code-image"></div>
              <b>使用微信打赏</b>
            </span>
          </span>
        </div>
        <p class="notice">若你觉得我的文章对你有帮助，欢迎点击上方按钮对我打赏</p>
      </div>
    
    <!-- 打赏 END -->
    <!-- 二维码 START -->
    
      <div class="qrcode">
        <canvas id="share-qrcode"></canvas>
        <p class="notice">扫描二维码，分享此文章</p>
      </div>
    
    <!-- 二维码 END -->
    
      <!-- Utterances START -->
      <div id="utterances"></div>
      <script src="https://utteranc.es/client.js"
        repo="ltyeamin/blogtalks"
        issue-term="pathname"
        theme="github-light"
        crossorigin="anonymous"
        async></script>    
      <!-- Utterances END -->
    
  </article>
  <!-- Article END -->
  <!-- Catalog START -->
  
    <aside class="catalog-container">
  <div class="toc-main">
    <strong class="toc-title">Catalog</strong>
    
      <ol class="toc-nav"><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#%E6%A8%A1%E6%8B%9F%E5%AE%9E%E7%8E%B0"><span class="toc-nav-text">模拟实现</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#InheritableThreadLocal%E5%BC%95%E5%85%A5"><span class="toc-nav-text">InheritableThreadLocal引入</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#%E6%B7%B1%E5%85%A5InheritableThreadLocal%E5%8E%9F%E7%90%86"><span class="toc-nav-text">深入InheritableThreadLocal原理</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#%E9%98%BF%E9%87%8C%E8%A7%A3%E5%86%B3%E4%B9%8B%E9%81%93"><span class="toc-nav-text">阿里解决之道</span></a></li></ol>
    
  </div>
</aside>
  
  <!-- Catalog END -->
</main>

<script>
  (function () {
    var url = 'http://example.com/2018/03/15/线程切换导致ThreadLocal数据丢失分析/';
    var banner = ''
    if (banner !== '' && banner !== 'undefined' && banner !== 'null') {
      $('#article-banner').css({
        'background-image': 'url(' + banner + ')'
      })
    } else {
      $('#article-banner').geopattern(url)
    }
    $('.header').removeClass('fixed-header')

    // error image
    $(".markdown-content img").on('error', function() {
      $(this).attr('src', '/css/images/error_icon.png')
      $(this).css({
        'cursor': 'default'
      })
    })

    // zoom image
    $(".markdown-content img").on('click', function() {
      var src = $(this).attr('src')
      if (src !== '/css/images/error_icon.png') {
        var imageW = $(this).width()
        var imageH = $(this).height()

        var zoom = ($(window).width() * 0.95 / imageW).toFixed(2)
        zoom = zoom < 1 ? 1 : zoom
        zoom = zoom > 2 ? 2 : zoom
        var transY = (($(window).height() - imageH) / 2).toFixed(2)

        $('body').append('<div class="image-view-wrap"><div class="image-view-inner"><img src="'+ src +'" /></div></div>')
        $('.image-view-wrap').addClass('wrap-active')
        $('.image-view-wrap img').css({
          'width': `${imageW}`,
          'transform': `translate3d(0, ${transY}px, 0) scale3d(${zoom}, ${zoom}, 1)`
        })
        $('html').css('overflow', 'hidden')

        $('.image-view-wrap').on('click', function() {
          $(this).remove()
          $('html').attr('style', '')
        })
      }
    })
  })();
</script>


  <script>
    var qr = new QRious({
      element: document.getElementById('share-qrcode'),
      value: document.location.href
    });
  </script>






    <div class="scroll-top">
  <span class="arrow-icon"></span>
</div>
    <footer class="app-footer">
  <p class="copyright">
    &copy; 2024 | Proudly powered by <a href="https://hexo.io" target="_blank">Hexo</a>
    <br>
    Theme by <a target="_blank" rel="noopener" href="https://github.com/ltyeamin">tong.li</a>
  </p>
</footer>

<script>
  function async(u, c) {
    var d = document, t = 'script',
      o = d.createElement(t),
      s = d.getElementsByTagName(t)[0];
    o.src = u;
    if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }
    s.parentNode.insertBefore(o, s);
  }
</script>
<script>
  async("https://cdn.staticfile.org/fastclick/1.0.6/fastclick.min.js", function(){
    FastClick.attach(document.body);
  })
</script>

<script>
  var hasLine = 'true';
  async("https://cdn.staticfile.org/highlight.js/9.12.0/highlight.min.js", function(){
    $('figure pre').each(function(i, block) {
      var figure = $(this).parents('figure');
      if (hasLine === 'false') {
        figure.find('.gutter').hide();
      }
      hljs.configure({useBR: true});
      var lang = figure.attr('class').split(' ')[1] || 'code';
      var codeHtml = $(this).html();
      var codeTag = document.createElement('code');
      codeTag.className = lang;
      codeTag.innerHTML = codeHtml;
      $(this).attr('class', '').empty().html(codeTag);
      figure.attr('data-lang', lang.toUpperCase());
      hljs.highlightBlock(block);
    });
  })
</script>
<!-- Baidu Tongji -->



<script src='https://cdn.staticfile.org/mermaid/8.11.2/mermaid.min.js'></script>



<script src="/js/script.js"></script>


  </body>
</html>